home *** CD-ROM | disk | FTP | other *** search
/ Aminet 40 / Aminet 40 (2000)(Schatztruhe)[!][Dec 2000].iso / Aminet / dev / lang / Python16_Src.lha / Python16_Source / Tools / idle / EditorWindow.py < prev    next >
Encoding:
Python Source  |  2000-05-10  |  22.7 KB  |  729 lines

  1. import sys
  2. import os
  3. import string
  4. import re
  5. import imp
  6. from Tkinter import *
  7. import tkSimpleDialog
  8. import tkMessageBox
  9. import BrowserControl
  10. import idlever
  11. import WindowList
  12. from IdleConf import idleconf
  13.  
  14. # The default tab setting for a Text widget, in average-width characters.
  15. TK_TABWIDTH_DEFAULT = 8
  16.  
  17. # File menu
  18.  
  19. #$ event <<open-module>>
  20. #$ win <Alt-m>
  21. #$ unix <Control-x><Control-m>
  22.  
  23. #$ event <<open-class-browser>>
  24. #$ win <Alt-c>
  25. #$ unix <Control-x><Control-b>
  26.  
  27. #$ event <<open-path-browser>>
  28.  
  29. #$ event <<close-window>>
  30. #$ unix <Control-x><Control-0>
  31. #$ unix <Control-x><Key-0>
  32. #$ win <Alt-F4>
  33.  
  34. # Edit menu
  35.  
  36. #$ event <<Copy>>
  37. #$ win <Control-c>
  38. #$ unix <Alt-w>
  39.  
  40. #$ event <<Cut>>
  41. #$ win <Control-x>
  42. #$ unix <Control-w>
  43.  
  44. #$ event <<Paste>>
  45. #$ win <Control-v>
  46. #$ unix <Control-y>
  47.  
  48. #$ event <<select-all>>
  49. #$ win <Alt-a>
  50. #$ unix <Alt-a>
  51.  
  52. # Help menu
  53.  
  54. #$ event <<help>>
  55. #$ win <F1>
  56. #$ unix <F1>
  57.  
  58. #$ event <<about-idle>>
  59.  
  60. # Events without menu entries
  61.  
  62. #$ event <<remove-selection>>
  63. #$ win <Escape>
  64.  
  65. #$ event <<center-insert>>
  66. #$ win <Control-l>
  67. #$ unix <Control-l>
  68.  
  69. #$ event <<do-nothing>>
  70. #$ unix <Control-x>
  71.  
  72.  
  73. about_title = "About IDLE"
  74. about_text = """\
  75. IDLE %s
  76.  
  77. An Integrated DeveLopment Environment for Python
  78.  
  79. by Guido van Rossum
  80. """ % idlever.IDLE_VERSION
  81.  
  82. class EditorWindow:
  83.  
  84.     from Percolator import Percolator
  85.     from ColorDelegator import ColorDelegator
  86.     from UndoDelegator import UndoDelegator
  87.     from IOBinding import IOBinding
  88.     import Bindings
  89.     from Tkinter import Toplevel
  90.     from MultiStatusBar import MultiStatusBar
  91.  
  92.     about_title = about_title
  93.     about_text = about_text
  94.  
  95.     vars = {}
  96.  
  97.     def __init__(self, flist=None, filename=None, key=None, root=None):
  98.         edconf = idleconf.getsection('EditorWindow')
  99.         coconf = idleconf.getsection('Colors')
  100.         self.flist = flist
  101.         root = root or flist.root
  102.         self.root = root
  103.         if flist:
  104.             self.vars = flist.vars
  105.         self.menubar = Menu(root)
  106.         self.top = top = self.Toplevel(root, menu=self.menubar)
  107.         self.vbar = vbar = Scrollbar(top, name='vbar')
  108.         self.text_frame = text_frame = Frame(top)
  109.         self.text = text = Text(text_frame, name='text', padx=5,
  110.                       foreground=coconf.getdef('normal-foreground'),
  111.                       background=coconf.getdef('normal-background'),
  112.                       highlightcolor=coconf.getdef('hilite-foreground'),
  113.                       highlightbackground=coconf.getdef('hilite-background'),
  114.                       insertbackground=coconf.getdef('cursor-background'),
  115.                       width=edconf.getint('width'),
  116.                       height=edconf.getint('height'),
  117.                       wrap="none")
  118.  
  119.         self.createmenubar()
  120.         self.apply_bindings()
  121.  
  122.         self.top.protocol("WM_DELETE_WINDOW", self.close)
  123.         self.top.bind("<<close-window>>", self.close_event)
  124.         text.bind("<<center-insert>>", self.center_insert_event)
  125.         text.bind("<<help>>", self.help_dialog)
  126.         text.bind("<<python-docs>>", self.python_docs)
  127.         text.bind("<<about-idle>>", self.about_dialog)
  128.         text.bind("<<open-module>>", self.open_module)
  129.         text.bind("<<do-nothing>>", lambda event: "break")
  130.         text.bind("<<select-all>>", self.select_all)
  131.         text.bind("<<remove-selection>>", self.remove_selection)
  132.         text.bind("<3>", self.right_menu_event)
  133.         if flist:
  134.             flist.inversedict[self] = key
  135.             if key:
  136.                 flist.dict[key] = self
  137.             text.bind("<<open-new-window>>", self.flist.new_callback)
  138.             text.bind("<<close-all-windows>>", self.flist.close_all_callback)
  139.             text.bind("<<open-class-browser>>", self.open_class_browser)
  140.             text.bind("<<open-path-browser>>", self.open_path_browser)
  141.  
  142.         vbar['command'] = text.yview
  143.         vbar.pack(side=RIGHT, fill=Y)
  144.  
  145.         text['yscrollcommand'] = vbar.set
  146.         text['font'] = edconf.get('font-name'), edconf.get('font-size')
  147.         text_frame.pack(side=LEFT, fill=BOTH, expand=1)
  148.         text.pack(side=TOP, fill=BOTH, expand=1)
  149.         text.focus_set()
  150.  
  151.         self.per = per = self.Percolator(text)
  152.         if self.ispythonsource(filename):
  153.             self.color = color = self.ColorDelegator(); per.insertfilter(color)
  154.             ##print "Initial colorizer"
  155.         else:
  156.             ##print "No initial colorizer"
  157.             self.color = None
  158.         self.undo = undo = self.UndoDelegator(); per.insertfilter(undo)
  159.         self.io = io = self.IOBinding(self)
  160.  
  161.         text.undo_block_start = undo.undo_block_start
  162.         text.undo_block_stop = undo.undo_block_stop
  163.         undo.set_saved_change_hook(self.saved_change_hook)
  164.         io.set_filename_change_hook(self.filename_change_hook)
  165.  
  166.         if filename:
  167.             if os.path.exists(filename):
  168.                 io.loadfile(filename)
  169.             else:
  170.                 io.set_filename(filename)
  171.  
  172.         self.saved_change_hook()
  173.  
  174.         self.load_extensions()
  175.  
  176.         menu = self.menudict.get('windows')
  177.         if menu:
  178.             end = menu.index("end")
  179.             if end is None:
  180.                 end = -1
  181.             if end >= 0:
  182.                 menu.add_separator()
  183.                 end = end + 1
  184.             self.wmenu_end = end
  185.             WindowList.register_callback(self.postwindowsmenu)
  186.  
  187.         # Some abstractions so IDLE extensions are cross-IDE
  188.         self.askyesno = tkMessageBox.askyesno
  189.         self.askinteger = tkSimpleDialog.askinteger
  190.         self.showerror = tkMessageBox.showerror
  191.  
  192.         if self.extensions.has_key('AutoIndent'):
  193.             self.extensions['AutoIndent'].set_indentation_params(
  194.                 self.ispythonsource(filename))
  195.         self.set_status_bar()
  196.  
  197.     def set_status_bar(self):
  198.         self.status_bar = self.MultiStatusBar(self.text_frame)
  199.         self.status_bar.set_label('column', 'Col: ?', side=RIGHT)
  200.         self.status_bar.set_label('line', 'Ln: ?', side=RIGHT)
  201.         self.status_bar.pack(side=BOTTOM, fill=X)
  202.         self.text.bind('<KeyRelease>', self.set_line_and_column)
  203.         self.text.bind('<ButtonRelease>', self.set_line_and_column)
  204.         self.text.after_idle(self.set_line_and_column)
  205.  
  206.     def set_line_and_column(self, event=None):
  207.         line, column = string.split(self.text.index(INSERT), '.')
  208.         self.status_bar.set_label('column', 'Col: %s' % column)
  209.         self.status_bar.set_label('line', 'Ln: %s' % line)
  210.  
  211.     def wakeup(self):
  212.         if self.top.wm_state() == "iconic":
  213.             self.top.wm_deiconify()
  214.         else:
  215.             self.top.tkraise()
  216.         self.text.focus_set()
  217.  
  218.     menu_specs = [
  219.         ("file", "_File"),
  220.         ("edit", "_Edit"),
  221.         ("windows", "_Windows"),
  222.         ("help", "_Help"),
  223.     ]
  224.  
  225.     def createmenubar(self):
  226.         mbar = self.menubar
  227.         self.menudict = menudict = {}
  228.         for name, label in self.menu_specs:
  229.             underline, label = prepstr(label)
  230.             menudict[name] = menu = Menu(mbar, name=name)
  231.             mbar.add_cascade(label=label, menu=menu, underline=underline)
  232.         self.fill_menus()
  233.  
  234.     def postwindowsmenu(self):
  235.         # Only called when Windows menu exists
  236.         # XXX Actually, this Just-In-Time updating interferes badly
  237.         # XXX with the tear-off feature.  It would be better to update
  238.         # XXX all Windows menus whenever the list of windows changes.
  239.         menu = self.menudict['windows']
  240.         end = menu.index("end")
  241.         if end is None:
  242.             end = -1
  243.         if end > self.wmenu_end:
  244.             menu.delete(self.wmenu_end+1, end)
  245.         WindowList.add_windows_to_menu(menu)
  246.  
  247.     rmenu = None
  248.  
  249.     def right_menu_event(self, event):
  250.         self.text.tag_remove("sel", "1.0", "end")
  251.         self.text.mark_set("insert", "@%d,%d" % (event.x, event.y))
  252.         if not self.rmenu:
  253.             self.make_rmenu()
  254.         rmenu = self.rmenu
  255.         self.event = event
  256.         iswin = sys.platform[:3] == 'win'
  257.         if iswin:
  258.             self.text.config(cursor="arrow")
  259.         rmenu.tk_popup(event.x_root, event.y_root)
  260.         if iswin:
  261.             self.text.config(cursor="ibeam")
  262.  
  263.     rmenu_specs = [
  264.         # ("Label", "<<virtual-event>>"), ...
  265.         ("Close", "<<close-window>>"), # Example
  266.     ]
  267.  
  268.     def make_rmenu(self):
  269.         rmenu = Menu(self.text, tearoff=0)
  270.         for label, eventname in self.rmenu_specs:
  271.             def command(text=self.text, eventname=eventname):
  272.                 text.event_generate(eventname)
  273.             rmenu.add_command(label=label, command=command)
  274.         self.rmenu = rmenu
  275.  
  276.     def about_dialog(self, event=None):
  277.         tkMessageBox.showinfo(self.about_title, self.about_text,
  278.                               master=self.text)
  279.  
  280.     helpfile = "help.txt"
  281.  
  282.     def help_dialog(self, event=None):
  283.         try:
  284.             helpfile = os.path.join(os.path.dirname(__file__), self.helpfile)
  285.         except NameError:
  286.             helpfile = self.helpfile
  287.         if self.flist:
  288.             self.flist.open(helpfile)
  289.         else:
  290.             self.io.loadfile(helpfile)
  291.  
  292.     help_url = "http://www.python.org/doc/current/"
  293.     if sys.platform[:3] == "win":
  294.         fn = os.path.dirname(os.path.dirname(os.path.dirname(__file__)))
  295.         fn = os.path.join(fn, "Doc", "index.html")
  296.         if os.path.isfile(fn):
  297.             help_url = fn
  298.         del fn
  299.  
  300.     def python_docs(self, event=None):
  301.         BrowserControl.open(self.help_url)
  302.  
  303.     def select_all(self, event=None):
  304.         self.text.tag_add("sel", "1.0", "end-1c")
  305.         self.text.mark_set("insert", "1.0")
  306.         self.text.see("insert")
  307.         return "break"
  308.  
  309.     def remove_selection(self, event=None):
  310.         self.text.tag_remove("sel", "1.0", "end")
  311.         self.text.see("insert")
  312.  
  313.     def open_module(self, event=None):
  314.         # XXX Shouldn't this be in IOBinding or in FileList?
  315.         try:
  316.             name = self.text.get("sel.first", "sel.last")
  317.         except TclError:
  318.             name = ""
  319.         else:
  320.             name = string.strip(name)
  321.         if not name:
  322.             name = tkSimpleDialog.askstring("Module",
  323.                      "Enter the name of a Python module\n"
  324.                      "to search on sys.path and open:",
  325.                      parent=self.text)
  326.             if name:
  327.                 name = string.strip(name)
  328.             if not name:
  329.                 return
  330.         # XXX Ought to support package syntax
  331.         # XXX Ought to insert current file's directory in front of path
  332.         try:
  333.             (f, file, (suffix, mode, type)) = imp.find_module(name)
  334.         except (NameError, ImportError), msg:
  335.             tkMessageBox.showerror("Import error", str(msg), parent=self.text)
  336.             return
  337.         if type != imp.PY_SOURCE:
  338.             tkMessageBox.showerror("Unsupported type",
  339.                 "%s is not a source module" % name, parent=self.text)
  340.             return
  341.         if f:
  342.             f.close()
  343.         if self.flist:
  344.             self.flist.open(file)
  345.         else:
  346.             self.io.loadfile(file)
  347.  
  348.     def open_class_browser(self, event=None):
  349.         filename = self.io.filename
  350.         if not filename:
  351.             tkMessageBox.showerror(
  352.                 "No filename",
  353.                 "This buffer has no associated filename",
  354.                 master=self.text)
  355.             self.text.focus_set()
  356.             return None
  357.         head, tail = os.path.split(filename)
  358.         base, ext = os.path.splitext(tail)
  359.         import ClassBrowser
  360.         ClassBrowser.ClassBrowser(self.flist, base, [head])
  361.  
  362.     def open_path_browser(self, event=None):
  363.         import PathBrowser
  364.         PathBrowser.PathBrowser(self.flist)
  365.  
  366.     def gotoline(self, lineno):
  367.         if lineno is not None and lineno > 0:
  368.             self.text.mark_set("insert", "%d.0" % lineno)
  369.             self.text.tag_remove("sel", "1.0", "end")
  370.             self.text.tag_add("sel", "insert", "insert +1l")
  371.             self.center()
  372.  
  373.     def ispythonsource(self, filename):
  374.         if not filename:
  375.             return 1
  376.         base, ext = os.path.splitext(os.path.basename(filename))
  377.         if os.path.normcase(ext) in (".py", ".pyw"):
  378.             return 1
  379.         try:
  380.             f = open(filename)
  381.             line = f.readline()
  382.             f.close()
  383.         except IOError:
  384.             return 0
  385.         return line[:2] == '#!' and string.find(line, 'python') >= 0
  386.  
  387.     def close_hook(self):
  388.         if self.flist:
  389.             self.flist.close_edit(self)
  390.  
  391.     def set_close_hook(self, close_hook):
  392.         self.close_hook = close_hook
  393.  
  394.     def filename_change_hook(self):
  395.         if self.flist:
  396.             self.flist.filename_changed_edit(self)
  397.         self.saved_change_hook()
  398.         if self.ispythonsource(self.io.filename):
  399.             self.addcolorizer()
  400.         else:
  401.             self.rmcolorizer()
  402.  
  403.     def addcolorizer(self):
  404.         if self.color:
  405.             return
  406.         ##print "Add colorizer"
  407.         self.per.removefilter(self.undo)
  408.         self.color = self.ColorDelegator()
  409.         self.per.insertfilter(self.color)
  410.         self.per.insertfilter(self.undo)
  411.  
  412.     def rmcolorizer(self):
  413.         if not self.color:
  414.             return
  415.         ##print "Remove colorizer"
  416.         self.per.removefilter(self.undo)
  417.         self.per.removefilter(self.color)
  418.         self.color = None
  419.         self.per.insertfilter(self.undo)
  420.  
  421.     def saved_change_hook(self):
  422.         short = self.short_title()
  423.         long = self.long_title()
  424.         if short and long:
  425.             title = short + " - " + long
  426.         elif short:
  427.             title = short
  428.         elif long:
  429.             title = long
  430.         else:
  431.             title = "Untitled"
  432.         icon = short or long or title
  433.         if not self.get_saved():
  434.             title = "*%s*" % title
  435.             icon = "*%s" % icon
  436.         self.top.wm_title(title)
  437.         self.top.wm_iconname(icon)
  438.  
  439.     def get_saved(self):
  440.         return self.undo.get_saved()
  441.  
  442.     def set_saved(self, flag):
  443.         self.undo.set_saved(flag)
  444.  
  445.     def reset_undo(self):
  446.         self.undo.reset_undo()
  447.  
  448.     def short_title(self):
  449.         filename = self.io.filename
  450.         if filename:
  451.             filename = os.path.basename(filename)
  452.         return filename
  453.  
  454.     def long_title(self):
  455.         return self.io.filename or ""
  456.  
  457.     def center_insert_event(self, event):
  458.         self.center()
  459.  
  460.     def center(self, mark="insert"):
  461.         text = self.text
  462.         top, bot = self.getwindowlines()
  463.         lineno = self.getlineno(mark)
  464.         height = bot - top
  465.         newtop = max(1, lineno - height/2)
  466.         text.yview(float(newtop))
  467.  
  468.     def getwindowlines(self):
  469.         text = self.text
  470.         top = self.getlineno("@0,0")
  471.         bot = self.getlineno("@0,65535")
  472.         if top == bot and text.winfo_height() == 1:
  473.             # Geometry manager hasn't run yet
  474.             height = int(text['height'])
  475.             bot = top + height - 1
  476.         return top, bot
  477.  
  478.     def getlineno(self, mark="insert"):
  479.         text = self.text
  480.         return int(float(text.index(mark)))
  481.  
  482.     def close_event(self, event):
  483.         self.close()
  484.  
  485.     def maybesave(self):
  486.         if self.io:
  487.             return self.io.maybesave()
  488.  
  489.     def close(self):
  490.         self.top.wm_deiconify()
  491.         self.top.tkraise()
  492.         reply = self.maybesave()
  493.         if reply != "cancel":
  494.             self._close()
  495.         return reply
  496.  
  497.     def _close(self):
  498.         WindowList.unregister_callback(self.postwindowsmenu)
  499.         if self.close_hook:
  500.             self.close_hook()
  501.         self.flist = None
  502.         colorizing = 0
  503.         self.unload_extensions()
  504.         self.io.close(); self.io = None
  505.         self.undo = None # XXX
  506.         if self.color:
  507.             colorizing = self.color.colorizing
  508.             doh = colorizing and self.top
  509.             self.color.close(doh) # Cancel colorization
  510.         self.text = None
  511.         self.vars = None
  512.         self.per.close(); self.per = None
  513.         if not colorizing:
  514.             self.top.destroy()
  515.  
  516.     def load_extensions(self):
  517.         self.extensions = {}
  518.         self.load_standard_extensions()
  519.  
  520.     def unload_extensions(self):
  521.         for ins in self.extensions.values():
  522.             if hasattr(ins, "close"):
  523.                 ins.close()
  524.         self.extensions = {}
  525.  
  526.     def load_standard_extensions(self):
  527.         for name in self.get_standard_extension_names():
  528.             try:
  529.                 self.load_extension(name)
  530.             except:
  531.                 print "Failed to load extension", `name`
  532.                 import traceback
  533.                 traceback.print_exc()
  534.  
  535.     def get_standard_extension_names(self):
  536.         return idleconf.getextensions()
  537.  
  538.     def load_extension(self, name):
  539.         mod = __import__(name, globals(), locals(), [])
  540.         cls = getattr(mod, name)
  541.         ins = cls(self)
  542.         self.extensions[name] = ins
  543.         kdnames = ["keydefs"]
  544.         if sys.platform == 'win32':
  545.             kdnames.append("windows_keydefs")
  546.         elif sys.platform == 'mac':
  547.             kdnames.append("mac_keydefs")
  548.         else:
  549.             kdnames.append("unix_keydefs")
  550.         keydefs = {}
  551.         for kdname in kdnames:
  552.             if hasattr(ins, kdname):
  553.                 keydefs.update(getattr(ins, kdname))
  554.         if keydefs:
  555.             self.apply_bindings(keydefs)
  556.             for vevent in keydefs.keys():
  557.                 methodname = string.replace(vevent, "-", "_")
  558.                 while methodname[:1] == '<':
  559.                     methodname = methodname[1:]
  560.                 while methodname[-1:] == '>':
  561.                     methodname = methodname[:-1]
  562.                 methodname = methodname + "_event"
  563.                 if hasattr(ins, methodname):
  564.                     self.text.bind(vevent, getattr(ins, methodname))
  565.         if hasattr(ins, "menudefs"):
  566.             self.fill_menus(ins.menudefs, keydefs)
  567.         return ins
  568.  
  569.     def apply_bindings(self, keydefs=None):
  570.         if keydefs is None:
  571.             keydefs = self.Bindings.default_keydefs
  572.         text = self.text
  573.         text.keydefs = keydefs
  574.         for event, keylist in keydefs.items():
  575.             if keylist:
  576.                 apply(text.event_add, (event,) + tuple(keylist))
  577.  
  578.     def fill_menus(self, defs=None, keydefs=None):
  579.         # Fill the menus. Menus that are absent or None in
  580.         # self.menudict are ignored.
  581.         if defs is None:
  582.             defs = self.Bindings.menudefs
  583.         if keydefs is None:
  584.             keydefs = self.Bindings.default_keydefs
  585.         menudict = self.menudict
  586.         text = self.text
  587.         for mname, itemlist in defs:
  588.             menu = menudict.get(mname)
  589.             if not menu:
  590.                 continue
  591.             for item in itemlist:
  592.                 if not item:
  593.                     menu.add_separator()
  594.                 else:
  595.                     label, event = item
  596.                     checkbutton = (label[:1] == '!')
  597.                     if checkbutton:
  598.                         label = label[1:]
  599.                     underline, label = prepstr(label)
  600.                     accelerator = get_accelerator(keydefs, event)
  601.                     def command(text=text, event=event):
  602.                         text.event_generate(event)
  603.                     if checkbutton:
  604.                         var = self.getrawvar(event, BooleanVar)
  605.                         menu.add_checkbutton(label=label, underline=underline,
  606.                             command=command, accelerator=accelerator,
  607.                             variable=var)
  608.                     else:
  609.                         menu.add_command(label=label, underline=underline,
  610.                             command=command, accelerator=accelerator)
  611.  
  612.     def getvar(self, name):
  613.         var = self.getrawvar(name)
  614.         if var:
  615.             return var.get()
  616.  
  617.     def setvar(self, name, value, vartype=None):
  618.         var = self.getrawvar(name, vartype)
  619.         if var:
  620.             var.set(value)
  621.  
  622.     def getrawvar(self, name, vartype=None):
  623.         var = self.vars.get(name)
  624.         if not var and vartype:
  625.             self.vars[name] = var = vartype(self.text)
  626.         return var
  627.  
  628.     # Tk implementations of "virtual text methods" -- each platform
  629.     # reusing IDLE's support code needs to define these for its GUI's
  630.     # flavor of widget.
  631.  
  632.     # Is character at text_index in a Python string?  Return 0 for
  633.     # "guaranteed no", true for anything else.  This info is expensive
  634.     # to compute ab initio, but is probably already known by the
  635.     # platform's colorizer.
  636.  
  637.     def is_char_in_string(self, text_index):
  638.         if self.color:
  639.             # Return true iff colorizer hasn't (re)gotten this far
  640.             # yet, or the character is tagged as being in a string
  641.             return self.text.tag_prevrange("TODO", text_index) or \
  642.                    "STRING" in self.text.tag_names(text_index)
  643.         else:
  644.             # The colorizer is missing: assume the worst
  645.             return 1
  646.  
  647.     # If a selection is defined in the text widget, return (start,
  648.     # end) as Tkinter text indices, otherwise return (None, None)
  649.     def get_selection_indices(self):
  650.         try:
  651.             first = self.text.index("sel.first")
  652.             last = self.text.index("sel.last")
  653.             return first, last
  654.         except TclError:
  655.             return None, None
  656.  
  657.     # Return the text widget's current view of what a tab stop means
  658.     # (equivalent width in spaces).
  659.  
  660.     def get_tabwidth(self):
  661.         current = self.text['tabs'] or TK_TABWIDTH_DEFAULT
  662.         return int(current)
  663.  
  664.     # Set the text widget's current view of what a tab stop means.
  665.  
  666.     def set_tabwidth(self, newtabwidth):
  667.         text = self.text
  668.         if self.get_tabwidth() != newtabwidth:
  669.             pixels = text.tk.call("font", "measure", text["font"],
  670.                                   "-displayof", text.master,
  671.                                   "n" * newtabwith)
  672.             text.configure(tabs=pixels)
  673.  
  674. def prepstr(s):
  675.     # Helper to extract the underscore from a string, e.g.
  676.     # prepstr("Co_py") returns (2, "Copy").
  677.     i = string.find(s, '_')
  678.     if i >= 0:
  679.         s = s[:i] + s[i+1:]
  680.     return i, s
  681.  
  682.  
  683. keynames = {
  684.  'bracketleft': '[',
  685.  'bracketright': ']',
  686.  'slash': '/',
  687. }
  688.  
  689. def get_accelerator(keydefs, event):
  690.     keylist = keydefs.get(event)
  691.     if not keylist:
  692.         return ""
  693.     s = keylist[0]
  694.     s = re.sub(r"-[a-z]\b", lambda m: string.upper(m.group()), s)
  695.     s = re.sub(r"\b\w+\b", lambda m: keynames.get(m.group(), m.group()), s)
  696.     s = re.sub("Key-", "", s)
  697.     s = re.sub("Control-", "Ctrl-", s)
  698.     s = re.sub("-", "+", s)
  699.     s = re.sub("><", " ", s)
  700.     s = re.sub("<", "", s)
  701.     s = re.sub(">", "", s)
  702.     return s
  703.  
  704.  
  705. def fixwordbreaks(root):
  706.     # Make sure that Tk's double-click and next/previous word
  707.     # operations use our definition of a word (i.e. an identifier)
  708.     tk = root.tk
  709.     tk.call('tcl_wordBreakAfter', 'a b', 0) # make sure word.tcl is loaded
  710.     tk.call('set', 'tcl_wordchars', '[a-zA-Z0-9_]')
  711.     tk.call('set', 'tcl_nonwordchars', '[^a-zA-Z0-9_]')
  712.  
  713.  
  714. def test():
  715.     root = Tk()
  716.     fixwordbreaks(root)
  717.     root.withdraw()
  718.     if sys.argv[1:]:
  719.         filename = sys.argv[1]
  720.     else:
  721.         filename = None
  722.     edit = EditorWindow(root=root, filename=filename)
  723.     edit.set_close_hook(root.quit)
  724.     root.mainloop()
  725.     root.destroy()
  726.  
  727. if __name__ == '__main__':
  728.     test()
  729.